﻿#region COPYRIGHT© 2009-2012 Phillip Clark. All rights reserved.

// For licensing information see License.txt (MIT style licensing).

#endregion

using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using FlitBit.Core;

namespace FlitBit.Proto
{
	/// <summary>
	///   Socket adapter that provides a simplified convention for working with
	///   overlapped IO.
	/// </summary>
	public class OverlappedSocketEndpoint : Disposable, INetEndpoint
	{
		static int __totalSequence;

		Socket _socket;

		/// <summary>
		///   Acquires async event args and attaches it to this object's
		///   IO_Completed method.
		/// </summary>
		/// <returns></returns>
		protected virtual SocketAsyncEventArgs AcquireAsyncEventArgs()
		{
			Contract.Ensures(Contract.Result<SocketAsyncEventArgs>() != null);

			var args = new SocketAsyncEventArgs();
			args.Completed += IO_Completed;
			return args;
		}

		/// <summary>
		///   Performs this object's disposal.
		/// </summary>
		/// <param name="disposing"></param>
		/// <returns></returns>
		protected override bool PerformDispose(bool disposing)
		{
			Thread.MemoryBarrier();
			var sock = _socket;
			Thread.MemoryBarrier();
			if (sock != null)
			{
				if (Interlocked.CompareExchange(ref _socket, null, sock) == sock)
				{
					if (sock.Connected)
					{
						try
						{
							sock.Close();
						}
						catch (IOException)
						{
							/* error eaten on purpose; may be called from the GC */
						}
					}

					try
					{
						sock.Dispose();
					}
					catch (ObjectDisposedException)
					{
						/* error eaten on purpose; may be called from the GC */
					}
				}
			}
			return disposing && sock != null;
		}

		void IO_Completed(object sender, SocketAsyncEventArgs e)
		{
			var compl = (Completion) e.UserToken;
			if (compl.Callback != null)
			{
				compl.Callback(null, new OverlappedOpState(this, e, compl.Sequence, Interlocked.Increment(ref __totalSequence)));
			}
		}

		#region INetEndpoint Members

		/// <summary>
		///   Gets the underlying endpoint.
		/// </summary>
		public IPEndPoint EndPoint
		{
			get
			{
				var sock = Util.VolatileRead(ref _socket);
				return sock == null ? null : sock.LocalEndPoint as IPEndPoint;
			}
		}

		/// <summary>
		///   Indicates whether the instance has been initialized with a socket.
		/// </summary>
		public bool IsInitialized { get { return Util.VolatileRead(ref _socket) != null; } }

		/// <summary>
		///   Initializes
		/// </summary>
		/// <param name="socket"></param>
		public void Initialize(Socket socket)
		{
			_socket = socket;
		}

		/// <summary>
		///   Accepts connections asynchronously
		/// </summary>
		/// <param name="endpoint"></param>
		/// <param name="callback"></param>
		public void AcceptAsync(EndPoint endpoint, Action<Exception, OverlappedOpState> callback)
		{
			var sock = Util.VolatileRead(ref _socket);

			var args = AcquireAsyncEventArgs();
			args.RemoteEndPoint = endpoint;
			args.UserToken = new Completion(Interlocked.Increment(ref __totalSequence), callback);

			var willRaiseEvent = sock.AcceptAsync(args);
			if (!willRaiseEvent)
			{
				IO_Completed(this, args);
			}
		}

		/// <summary>
		///   Connects to an endpoint asynchronously.
		/// </summary>
		/// <param name="endpoint"></param>
		/// <param name="callback"></param>
		public void ConnectAsync(EndPoint endpoint, Action<Exception, OverlappedOpState> callback)
		{
			var sock = Util.VolatileRead(ref _socket);

			var args = AcquireAsyncEventArgs();
			args.RemoteEndPoint = endpoint;
			args.UserToken = new Completion(Interlocked.Increment(ref __totalSequence), callback);

			var willRaiseEvent = sock.ConnectAsync(args);
			if (!willRaiseEvent)
			{
				IO_Completed(this, args);
			}
		}

		/// <summary>
		///   Receives from an endpoint asynchronously.
		/// </summary>
		/// <param name="endpoint"></param>
		/// <param name="data"></param>
		/// <param name="offset"></param>
		/// <param name="count"></param>
		/// <returns></returns>
		public int ReceiveFrom(ref EndPoint endpoint, byte[] data, int offset, int count)
		{
			var sock = Util.VolatileRead(ref _socket);

			return sock.ReceiveFrom(data, offset, count, SocketFlags.None, ref endpoint);
		}

		/// <summary>
		///   Receives from an endpoint asynchronously.
		/// </summary>
		/// <param name="endpoint"></param>
		/// <param name="data"></param>
		/// <param name="offset"></param>
		/// <param name="count"></param>
		/// <param name="callback"></param>
		public void ReceiveFromAsync(EndPoint endpoint, byte[] data, int offset, int count,
			Action<Exception, OverlappedOpState> callback)
		{
			var sock = Util.VolatileRead(ref _socket);

			var args = AcquireAsyncEventArgs();
			args.RemoteEndPoint = endpoint;
			args.SetBuffer(data, offset, count);
			args.UserToken = new Completion(Interlocked.Increment(ref __totalSequence), callback);

			var willRaiseEvent = sock.ReceiveFromAsync(args);
			if (!willRaiseEvent)
			{
				IO_Completed(this, args);
			}
		}

		/// <summary>
		///   Sends data to an endpoint.
		/// </summary>
		/// <param name="endpoint"></param>
		/// <param name="data"></param>
		public void SendTo(EndPoint endpoint, byte[] data)
		{
			var sock = Util.VolatileRead(ref _socket);

			sock.SendTo(data, endpoint);
		}

		/// <summary>
		///   Sends data to an endpoint asynchronously.
		/// </summary>
		/// <param name="endpoint"></param>
		/// <param name="data"></param>
		/// <param name="count"></param>
		public void SendTo(EndPoint endpoint, byte[] data, int count)
		{
			var sock = Util.VolatileRead(ref _socket);
			sock.SendTo(data, count, SocketFlags.None, endpoint);
		}

		/// <summary>
		///   Sends data to an endpoint asynchronously.
		/// </summary>
		/// <param name="endpoint"></param>
		/// <param name="data"></param>
		/// <param name="offset"></param>
		/// <param name="count"></param>
		/// <param name="flags"></param>
		public void SendTo(EndPoint endpoint, byte[] data, int offset, int count, SocketFlags flags)
		{
			var sock = Util.VolatileRead(ref _socket);
			sock.SendTo(data, offset, count, flags, endpoint);
		}

		/// <summary>
		///   Sends data to an endpoint asynchronously.
		/// </summary>
		/// <param name="endpoint"></param>
		/// <param name="data"></param>
		/// <param name="count"></param>
		/// <param name="callback"></param>
		public void SendToAsync(EndPoint endpoint, byte[] data, int count, Action<Exception, OverlappedOpState> callback)
		{
			SendToAsync(endpoint, data, 0, count, callback);
		}

		/// <summary>
		///   Sends data to an endpoint asynchronously.
		/// </summary>
		/// <param name="endpoint"></param>
		/// <param name="data"></param>
		/// <param name="offset"></param>
		/// <param name="count"></param>
		/// <param name="callback"></param>
		public void SendToAsync(EndPoint endpoint, byte[] data, int offset, int count,
			Action<Exception, OverlappedOpState> callback)
		{
			var sock = Util.VolatileRead(ref _socket);

			var args = AcquireAsyncEventArgs();
			args.RemoteEndPoint = endpoint;
			args.SetBuffer(data, offset, count);
			args.UserToken = new Completion(Interlocked.Increment(ref __totalSequence), callback);

			var willRaiseEvent = sock.SendToAsync(args);
			if (!willRaiseEvent)
			{
				IO_Completed(this, args);
			}
		}

		/// <summary>
		///   Releases an async event args previously acquired.
		/// </summary>
		/// <param name="args"></param>
		public virtual void ReleaseAsyncEventArgs(SocketAsyncEventArgs args)
		{
			args.Completed -= IO_Completed;
			args.Dispose();
		}

		#endregion

		class Completion
		{
			public readonly Action<Exception, OverlappedOpState> Callback;
			public readonly int Sequence;

			internal Completion(int sequence, Action<Exception, OverlappedOpState> cb)
			{
				Callback = cb;
				Sequence = sequence;
			}
		}
	}
}